package com.quanquan.user.service;

import DTO.PageDTO;
import com.quanquan.user.configBeans.RabbitSender;
import com.quanquan.user.dto.SignInDTO;
import com.quanquan.user.entity.ConsecutiveRecord;
import com.quanquan.user.entity.SignIn;
import com.quanquan.user.entity.SignInRule;
import com.quanquan.user.entity.User;
import com.quanquan.user.exception.SignInRuleException;
import com.quanquan.user.repository.ConsecutiveRecordRepository;
import com.quanquan.user.repository.SignInRepository;
import com.quanquan.user.request.IntegralDetailsRequestBody;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.core.internal.Function;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import request.PageRequestBody;
import utils.DateUtils;
import utils.StringUtils;
import utils.UUIDGenerator;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static java.lang.Integer.valueOf;

/**
 * 功能描述:签到逻辑层
 * <p>
 * 1.主要实现逻辑
 * 主要节点：1.判断当天第一次登陆
 * 2.判断连着签到多少天
 * 3.送出积分
 *
 * @ClassName: SignInService
 * @Author: xiaoqianh丶
 * @Date: 2019-04-05 19:01
 * @Version: V1.0
 */
@Service
public class SignInService {


    private static final String SIGN_IN = "_signIn";
    public static final String REWARD_SIGN = "reward_sign";
    private final SignInRepository signInRepository;

    private final StringRedisTemplate redisTemplate;

    private final SignInRuleService signInRuleService;

    private final IntegralTradingDetailsService integralTradingDetailsService;

    private final RabbitSender rabbitSender;

    private final UserService userService;

    @Autowired
    private ConsecutiveRecordRepository consecutiveRecordRepository;

    @Autowired
    public SignInService(SignInRepository signInRepository, StringRedisTemplate redisTemplate, RabbitSender rabbitSender, IntegralTradingDetailsService integralTradingDetailsService, SignInRuleService signInRuleService, UserService userService) {
        this.signInRepository = signInRepository;
        this.redisTemplate = redisTemplate;
        this.rabbitSender = rabbitSender;
        this.integralTradingDetailsService = integralTradingDetailsService;
        this.signInRuleService = signInRuleService;
        this.userService = userService;
    }

    public PageDTO<SignInDTO> pagedList(PageRequestBody requestBody) {
        Integer page = requestBody.getPage() == null | requestBody.getPage() < 0 ? 0 : requestBody.getPage();
        Integer size = requestBody.getSize() == null ? 10 : requestBody.getSize();
        String keyword = requestBody.getKeyword();
        String fromDate = requestBody.getFromDate();
        String toDate = requestBody.getToDate();
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");
        List<String> userIds = new ArrayList<>();
        List<User> listByKeyword = new ArrayList<>();
        if (StringUtils.isNotEmpty(keyword)) {
            listByKeyword = userService.findListByKeyword(keyword);
            if (null != listByKeyword && !listByKeyword.isEmpty()) {
                listByKeyword.forEach(user -> userIds.add(user.getId()));
            }
        }
        Pageable pageable = PageRequest.of(page, size, sort);
        Page<SignIn> paged = signInRepository.findAll((Specification<SignIn>) (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            // 创建具体查询条件
            if (StringUtils.isNotEmpty(toDate)) {
                Date to = DateUtils.getEndOfDay(DateUtils.strToDate(toDate, DateUtils.DATE));
                predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createTime").as(Date.class), to));
            }
            if (StringUtils.isNotEmpty(fromDate)) {
                Date from = DateUtils.strToDate(fromDate, DateUtils.DATE);
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createTime").as(Date.class), from));
            }
            if (!userIds.isEmpty()) {
                CriteriaBuilder.In<String> in = criteriaBuilder.in(root.get("userId"));
                userIds.forEach(userId -> in.value(userId));
                predicates.add(in);
            }
            predicates.add(criteriaBuilder.equal(root.get("remove"), 0));
            return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
        }, pageable);
        List<SignIn> content = paged.getContent();
        //取出所有UserId并去重
        Set<String> userIdss = content.parallelStream().filter(distinctByKey(SignIn::getUserId)).map(SignIn::getUserId).collect(Collectors.toSet());

        List<SignInDTO> signInDTOS = new ArrayList<>();

        for (SignIn signIn : content) {
            SignInDTO signInDTO = new SignInDTO();
            BeanUtils.copyProperties(signIn, signInDTO);
            signInDTO.setSignInId(signIn.getId());
            signInDTOS.add(signInDTO);
        }
        List<User> users = userService.findUsers(userIdss);

        //将积分的关于用户的信息补上
        for (User user : users) {
            for (SignInDTO signInDTO : signInDTOS) {
                if ((user.getId()).equals(signInDTO.getUserId())) {
                    signInDTO.setPhone(user.getPhone());
                    signInDTO.setIntegral(user.getIntegral());
                    signInDTO.setPhone(user.getPhone());
                    signInDTO.setMemberName(user.getMemberName());
                    signInDTO.setMemberCardNum(user.getMemberCardNum());
                }
            }
        }

        PageDTO<SignInDTO> pageDTO = new PageDTO<>();
        pageDTO.setList(signInDTOS);
        pageDTO.setPage(page);
        pageDTO.setPageSize(size);
        pageDTO.setKeyword(keyword);
        pageDTO.setFromDate(fromDate);
        pageDTO.setToDate(toDate);
        pageDTO.setTotalPage(paged.getTotalPages());
        pageDTO.setTotal(paged.getTotalElements());
        return pageDTO;
    }

    /**
     * 判断当天是否签到过，签过就直接返回结果，没有就创建签到返回结果，创建后直接将改用户色数据缓存到redis
     *
     * @return
     */
    public int signInResult(String userId) throws SignInRuleException {
        List<SignIn> users = signInRepository.findByUserIdOrderByCreateTimeDesc(userId);
        String nowTime = DateUtils.parseDate2Str("yyyy-MM-dd", new Date());
        int totalPointDay = 0;
        if (users.size() > 0) {
            //会员的最新签到时间（与当前时间进行比较）
            Optional<String> result = users.stream().findFirst().map(SignIn::getCreateTime);
            String createTime = result.get();

            if (nowTime.equals(createTime)) {
                expirationTime(userId);
                return 0;
            } else {
                totalPointDay = signInAndGetIntegral(userId, nowTime);

                return totalPointDay;
            }

        }
        //查看是否同一天
        else {
            totalPointDay = signInAndGetIntegral(userId, nowTime);

            return totalPointDay;
        }
    }

    private int signInAndGetIntegral(String userId, String nowTime) throws SignInRuleException {
        SignIn signIn = new SignIn();
        signIn.setId(UUIDGenerator.getUUID());
        signIn.setUserId(userId);
        signIn.setOverdue(0);
        signIn.setCreateTime(nowTime);
        signIn.setRemove(0);
        signInRepository.save(signIn);
        expirationTime(userId);
        return giveIntegral(userId);
    }

    /**
     * 签到送积分
     *
     * @param userId
     * @return
     */
    private int giveIntegral(String userId) throws SignInRuleException {
        //赠送活动积分
        IntegralDetailsRequestBody requestBody = new IntegralDetailsRequestBody();
        SignInRule signInRule = signInRuleService.findSignInRule();
        if (signInRule == null) {
            return 0;
        }
        requestBody.setIntegralPoint(signInRule.getIntegralPoint());
        requestBody.setTradingContent("每日签到积分");
        requestBody.setConsumeType("user");
        requestBody.setTradeAction(0);
        requestBody.setSource("signIn");
        //签到规则的id
        requestBody.setForeignKeyId(signInRule.getId());
        requestBody.setMoney("0");
        requestBody.setUserId(userId);
        requestBody.setOrderNum(signInRule.getId());
        integralTradingDetailsService.createIntegralDetails(requestBody);

        //1.根据会员id查询会员的签到记录（未过期的）
        List<SignIn> userSignInResults = signInRepository.findByUserIdAndOverdueOrderByCreateTimeDesc(userId, 0);
        //第一次连签天数
        Integer days = valueOf(signInRule.getConsecutiveDay());
        //第二次连签天数
        Integer secondDays = valueOf(signInRule.getConsecutiveSecondDay());

        //每日奖励积分数
        int everyDayPoint = Integer.parseInt(signInRule.getIntegralPoint());
        //连续签到奖励积分
        ConsecutiveRecord consecutiveRecord = null;
        int bonusPoints = 0;
        if (userSignInResults.size() >= days) {
//            sendMessageToMQ(userId, signInRule);
            //判断是否完成第一次连签任务
            //根据userId查询连签记录
            consecutiveRecord = consecutiveRecordRepository.findByUserId(userId);
            if (consecutiveRecord == null) {
                //第一次参加连签
                bonusPoints = getBonusPointss(userId, signInRule, days, bonusPoints, 0);
            }
            if (consecutiveRecord != null) {
                String createTime = consecutiveRecord.getCreateTime();
                Integer status = consecutiveRecord.getStatus();
                String now = DateUtils.currentDate();
                //判断时间是否在当前月内
                boolean b = DateUtils.checkSameMonth(createTime, now);
                if ((b == true) && (status == 1)) {
                    //这个月参加过第一次连签
                    if (userSignInResults.size() >= secondDays) {
                        bonusPoints = getBonusPointss(userId, signInRule, secondDays, bonusPoints, status);

                    }
                }
                if ((b == true) && (status == 2)) {
                    //这个月已经参加过两次签到
                    //将该用户所有记录设为过期
                    List<String> overDueSignInIds = userSignInResults.parallelStream().map(SignIn::getId).collect(Collectors.toList());
                    signInResultOverDue(overDueSignInIds);
                    bonusPoints = 0;
                }
                if (!b) {
                    //已经过了一个月，重新参加 传3 指重新参加
                    bonusPoints = getBonusPointss(userId, signInRule, days, bonusPoints, 3);
                }

            }

        }

        //每天获取到的总积分
        return everyDayPoint + bonusPoints;
    }

    private int getBonusPointss(String userId, SignInRule signInRule, Integer days, int bonusPoints, int i) {

        String rewardPoint = checkSignContinuous(userId, signInRule, days, i);
        if (StringUtils.isNotEmpty(rewardPoint)) {
            bonusPoints = Integer.parseInt(rewardPoint);
        }
        return bonusPoints;
    }

    /**
     * to do 暂时不需要
     * 发送消息到队列
     *
     * @param userId
     * @param signInRule
     */
    private void sendMessageToMQ(String userId, SignInRule signInRule) {
        //通过队列来检查是否连续签到
        Map<String, Object> properties = new HashMap<>();
        properties.put("userId", userId);
        properties.put("bonusPoints", signInRule.getBonusPoints());
        properties.put("consecutiveDay", signInRule.getConsecutiveDay());


        //将计算结果放到MQ队列去补充明细的余额
        String exchange = "signIn.exchange";
        String routingKey = "signIn.continuous";
        rabbitSender.sendMessageToMQ(userId, properties, exchange, routingKey);
    }

    /**
     * 缓存上结果，并设置自动过期时间
     *
     * @param userId
     */
    private void expirationTime(String userId) {
        //将签到结果缓存到redis上key值是用户的id
        String signKey = userId + SIGN_IN;

        //当天的晚上12点
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        long timeInMillis = calendar.getTimeInMillis();
        //两者比较拿出相差的秒数
        long systime = System.currentTimeMillis();//当前系统时间
        int expirTime = (int) (timeInMillis - systime) / 1000;//相差毫秒数
        Integer expir = valueOf(expirTime);
        String nowTime = DateUtils.parseDate2Str("yyyy-MM-dd", new Date());
        redisTemplate.opsForValue().set(signKey, nowTime, expir, TimeUnit.SECONDS);
    }

    /**
     * 判断会员是否连续签到
     *
     * @param userId
     * @param signInRule
     */
    public String checkSignContinuous(String userId, SignInRule signInRule, Integer days, Integer status) {
        String reward = null;
        String bonusPoints = signInRule.getBonusPoints();
        String bonusSecondPoints = signInRule.getBonusSecondPoints();
        //1.根据会员id查询会员的签到记录（未过期的）
        List<SignIn> userSignInResults = signInRepository.findByUserIdAndOverdueOrderByCreateTimeDesc(userId, 0);

        if (userSignInResults.size() >= days) {
            List<String> createTimes = userSignInResults.stream().limit(days).map(SignIn::getCreateTime).collect(Collectors.toList());
            Optional<String> first = createTimes.stream().findFirst();
            String latestTime = first.get();
            List<String> skip = createTimes.stream().skip(days - 1).collect(Collectors.toList());
            String oldTime = skip.get(0);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            //判断两个日期是否在同一个月内
            boolean b = DateUtils.checkSameMonth(latestTime, oldTime);
            if (b) {
                Date date = null;
                try {
                    date = sdf.parse(oldTime);
                } catch (ParseException e) {
                    e.printStackTrace();
                }

                Calendar calendar = Calendar.getInstance();

                calendar.setTime(date);
                calendar.add(Calendar.DAY_OF_MONTH, days - 1);
                String computationTime = sdf.format(calendar.getTime());

                if (latestTime.equals(computationTime)) {
                    if (signInRule.getRewardType() == 0) {
                        sendIntegral(userId, signInRule, bonusPoints);

                        reward = bonusPoints;
                    }
                    if (signInRule.getRewardType() == 1) {
                        //发券
                        redisTemplate.opsForValue().set(REWARD_SIGN, "1", 6, TimeUnit.HOURS);
                        reward = "0";
                    }

                    if (signInRule.getRewardType() == 2) {
                        //发连接
                        redisTemplate.opsForValue().set(REWARD_SIGN, "2", 6, TimeUnit.HOURS);

                        reward = "0";

                    }
                    if (status == 0) {
                        //记录是否完成过连签
                        //头一回连签
                        ConsecutiveRecord consecutiveRecord = new ConsecutiveRecord();
                        consecutiveRecord.setId(UUIDGenerator.getUUID());
                        consecutiveRecord.setCreateTime(DateUtils.currentDate());
                        consecutiveRecord.setUserId(userId);
                        consecutiveRecord.setStatus(1);
                        consecutiveRecordRepository.save(consecutiveRecord);

                        //发送任务队列，完成签到任务

                        Map<String, Object> properties = new HashMap<>();
                        properties.put("userId", userId);
                        properties.put("taskType", "sign");

                        //将计算结果放到MQ队列去补充明细的余额
                        String msg = "补全积分明细余额";
                        String exchange = "task.exchange";
                        String routingKey = "task.sign";
                        rabbitSender.sendMessageToMQ(msg, properties, exchange, routingKey);
                    }
                    if (status == 1) {
                        //该月第二次参与
                        ConsecutiveRecord consecutiveRecord = consecutiveRecordRepository.findByUserId(userId);
                        consecutiveRecord.setStatus(2);
                        consecutiveRecordRepository.save(consecutiveRecord);
                        //发送第二轮积分奖励
                        sendIntegral(userId, signInRule, bonusSecondPoints);
                        //把记录设为过期，发积分
                        //失效的记录
                        List<String> overDueSignInIds = userSignInResults.stream().skip(userSignInResults.size() - days).map(SignIn::getId).collect(Collectors.toList());
                        signInResultOverDue(overDueSignInIds);
                        reward = bonusSecondPoints;
                    }
                    if (status == 3) {
                        //每月重新开始获得奖励
                        ConsecutiveRecord consecutiveRecord = consecutiveRecordRepository.findByUserId(userId);
                        consecutiveRecord.setCreateTime(DateUtils.currentDate());
                        consecutiveRecord.setStatus(1);
                        consecutiveRecordRepository.save(consecutiveRecord);
                        //发送第二轮积分奖励
                        sendIntegral(userId, signInRule, bonusPoints);
                        reward = bonusPoints;
                    }


                }
            }


        }
        return reward;
    }

    private void sendIntegral(String userId, SignInRule signInRule, String bonusPoints) {
        IntegralDetailsRequestBody requestBody = new IntegralDetailsRequestBody();
        //加积分
        requestBody.setIntegralPoint(bonusPoints);
        requestBody.setTradingContent("连续签到奖励积分");
        requestBody.setConsumeType("user");
        requestBody.setTradeAction(0);
        requestBody.setSource("signIn");
        //签到规则的id
        requestBody.setForeignKeyId(signInRule.getId());
        requestBody.setMoney("0");
        requestBody.setUserId(userId);
        requestBody.setOrderNum(signInRule.getId());
        integralTradingDetailsService.createIntegralDetails(requestBody);
    }


    private void signInResultOverDue(List<String> signInIds) {
        for (String signInId : signInIds) {
            SignIn signIn = signInRepository.findByIdAndOverdue(signInId, 0);
            signIn.setOverdue(1);
            signInRepository.save(signIn);
        }
    }

    /**
     * 根据用户的id查询用户当月的签到记录
     *
     * @param userId
     */
    public List<SignIn> findAllPersionSignIn(String userId) {
        return signInRepository.findAllByUserIdOrderByCreateTime(userId);
    }

    private static <T> java.util.function.Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }

    // todo 定时任务删除前一个月的所有记录
}
